/*
 Copyright 2009-2010 AdMob, Inc.

 Licensed under the Apache License, Version 2.0 (the "License");
 you may not use this file except in compliance with the License.
 You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

 Unless required by applicable law or agreed to in writing, software
 distributed under the License is distributed on an "AS IS" BASIS,
 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 See the License for the specific language governing permissions and
 limitations under the License.
 */

package com.adwhirl;

import java.io.IOException;
import java.lang.ref.WeakReference;
import java.util.Locale;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.DefaultHttpClient;

import android.app.Activity;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.ActivityInfo;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.AttributeSet;
import android.util.Log;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.widget.RelativeLayout;

import com.adwhirl.adapters.AdWhirlAdapter;
import com.adwhirl.obj.Custom;
import com.adwhirl.obj.Extra;
import com.adwhirl.obj.Ration;
import com.adwhirl.util.AdWhirlUtil;

public class AdWhirlLayout extends RelativeLayout {
	private static final String DEFAULT_MARKET_PREFIX = "market://";
	private static final String DEFAULT_MARKET_PATTERN = "market://details\\?id=([\\w\\.]+\\w).*";

	public static final String ADWHIRL_KEY = "ADWHIRL_KEY";
	public WeakReference<Activity> activityReference;

	// Only the UI thread can update the UI, so we need this for UI callbacks
	public final Handler handler = new Handler();

	// We also need a scheduler for background threads
	public final ScheduledExecutorService scheduler = Executors
			.newScheduledThreadPool(1);

	private String keyAdWhirl;
	public Extra extra;

	// The current custom ad
	public Custom custom;

	// This is just so our threads can reference us explicitly
	public WeakReference<RelativeLayout> superViewReference;

	// Added so we can tell the previous adapter that it is being destroyed.
	private AdWhirlAdapter previousAdapter;
	private AdWhirlAdapter currentAdapter;

	public Ration activeRation;
	public Ration nextRation;

	public AdWhirlInterface adWhirlInterface;

	public AdWhirlManager adWhirlManager;

	private boolean hasWindow;
	private boolean isScheduled;

	private int maxWidth;

	// IC
	private WeakReference<AdWhirlListener> adWhirlListenerRef;
	// IC
	private final Pattern defaultMarketPattern = Pattern
			.compile(DEFAULT_MARKET_PATTERN);
	// IC
	private final Pattern marketPattern = Pattern.compile(getResources()
			.getString(R.string.adwhirl_market_internal_app_url_parser));

	public void setAdWhirlListener(final AdWhirlListener adWhirlListener) {
		this.adWhirlListenerRef = new WeakReference<AdWhirlListener>(
				adWhirlListener);
	}

	public AdWhirlListener getAdWhirlListener() {
		final WeakReference<AdWhirlListener> ref = adWhirlListenerRef;
		if (ref == null) {
			return null;
		}
		return ref.get();
	}

	public void setMaxWidth(final int width) {
		maxWidth = width;
	}

	private int maxHeight;

	public void setMaxHeight(final int height) {
		maxHeight = height;
	}

	public AdWhirlLayout(final Activity context, final String keyAdWhirl) {
		super(context);
		init(context, keyAdWhirl);
	}

	public AdWhirlLayout(final Context context, final AttributeSet attrs) {
		super(context, attrs);
		// Retrieves AdWhirl key.
		final String key = getAdWhirlKey(context);
		init((Activity) context, key);
	}

	protected String getAdWhirlKey(final Context context) {
		final String packageName = context.getPackageName();
		final String activityName = context.getClass().getName();
		final PackageManager pm = context.getPackageManager();
		Bundle bundle = null;
		// Attempts to retrieve Activity-specific AdWhirl key first. If not
		// found, retrieve Application-wide AdWhirl key.
		try {
			final ActivityInfo activityInfo = pm.getActivityInfo(
					new ComponentName(packageName, activityName),
					PackageManager.GET_META_DATA);
			bundle = activityInfo.metaData;
			if (bundle != null) {
				return bundle.getString(AdWhirlLayout.ADWHIRL_KEY);
			}
		} catch (final NameNotFoundException exception) {
			// Activity cannot be found. Shouldn't be here.
			return null;
		}

		try {
			final ApplicationInfo appInfo = pm.getApplicationInfo(packageName,
					PackageManager.GET_META_DATA);
			bundle = appInfo.metaData;
			if (bundle != null) {
				return bundle.getString(AdWhirlLayout.ADWHIRL_KEY);
			}
		} catch (final NameNotFoundException exception) {
			// Application cannot be found. Shouldn't be here.
			return null;
		}
		return null;
	}

	protected void init(final Activity context, final String keyAdWhirl) {
		this.activityReference = new WeakReference<Activity>(context);
		this.superViewReference = new WeakReference<RelativeLayout>(this);
		this.keyAdWhirl = keyAdWhirl;
		this.hasWindow = true;
		this.isScheduled = true;

		// // IC : read from manifest
		// try {
		// final ApplicationInfo ai = context.getPackageManager()
		// .getApplicationInfo(context.getPackageName(),
		// PackageManager.GET_META_DATA);
		// final String value = ai.metaData.getString("ADWHIRL_MARKET_PREFIX");
		// Log.w(AdWhirlUtil.ADWHIRL, "READ VALUE: " + value);
		//
		// } catch (final NameNotFoundException e) {
		// Log.w(AdWhirlUtil.ADWHIRL,
		// "Caught an exception during meta attributes reading");
		// }

		scheduler.schedule(new InitRunnable(this, keyAdWhirl), 0,
				TimeUnit.SECONDS);

		setHorizontalScrollBarEnabled(false);
		setVerticalScrollBarEnabled(false);

		this.maxWidth = 0;
		this.maxHeight = 0;
	}

	@Override
	protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
		final int widthSize = MeasureSpec.getSize(widthMeasureSpec);
		final int heightSize = MeasureSpec.getSize(heightMeasureSpec);

		if (maxWidth > 0 && widthSize > maxWidth) {
			widthMeasureSpec = MeasureSpec.makeMeasureSpec(maxWidth,
					MeasureSpec.AT_MOST);
		}

		if (maxHeight > 0 && heightSize > maxHeight) {
			heightMeasureSpec = MeasureSpec.makeMeasureSpec(maxHeight,
					MeasureSpec.AT_MOST);
		}

		super.onMeasure(widthMeasureSpec, heightMeasureSpec);
	}

	@Override
	protected void onWindowVisibilityChanged(final int visibility) {
		if (visibility == VISIBLE) {
			this.hasWindow = true;
			if (!this.isScheduled) {
				this.isScheduled = true;

				if (this.extra != null) {
					rotateThreadedNow();
				} else {
					scheduler.schedule(new InitRunnable(this, keyAdWhirl), 0,
							TimeUnit.SECONDS);
				}
			}
		} else {
			this.hasWindow = false;
		}
	}

	private void rotateAd() {
		// IC
		// if (!this.hasWindow) {
		if (!isAdLive()) {
			this.isScheduled = false;

			return;
		}

		Log.i(AdWhirlUtil.ADWHIRL, "Rotating Ad");
		nextRation = adWhirlManager.getRation();

		handler.post(new HandleAdRunnable(this));
	}

	// Initialize the proper ad view from nextRation
	private void handleAd() {
		// We shouldn't ever get to a state where nextRation is null unless all
		// networks fail
		if (nextRation == null) {
			Log.e(AdWhirlUtil.ADWHIRL, "nextRation is null!");
			rotateThreadedDelayed();
			return;
		}

		final String rationInfo = String
				.format(Locale.ENGLISH,
						"Showing ad:\n\tnid: %s\n\tname: %s\n\ttype: %d\n\tkey: %s\n\tkey2: %s",
						nextRation.nid, nextRation.name, nextRation.type,
						nextRation.key, nextRation.key2);
		Log.d(AdWhirlUtil.ADWHIRL, rationInfo);

		try {
			// Tell the previous adapter that its view will be destroyed.
			if (this.previousAdapter != null) {
				this.previousAdapter.willDestroy();
			}
			this.previousAdapter = this.currentAdapter;
			this.currentAdapter = AdWhirlAdapter.handle(this, nextRation);
		} catch (final Throwable t) {
			Log.w(AdWhirlUtil.ADWHIRL, "Caught an exception in adapter:", t);
			rollover();
			return;
		}
	}

	// Rotate immediately
	public void rotateThreadedNow() {
		// IC
		if (scheduler != null) {
			scheduler.schedule(new RotateAdRunnable(this), 0, TimeUnit.SECONDS);
		}
	}

	// IC added
	private boolean isAdLive() {
		if (!this.hasWindow) {
			return false;
		}
		final WeakReference<Activity> aReference = this.activityReference;
		if (aReference == null) {
			return false;
		}
		final Activity activity = aReference.get();
		if (activity == null || activity.isFinishing()) {
			return false;
		}
		return true;
	}

	// IC added destroy method
	public void destroy() {
		this.handler.post(new HandleDestroy(this));
	}

	// IC added destroy method
	public void handleDestroy() {
		// Tell the previous adapter that its view will be destroyed.
		destroyAdapterQuietely(this.previousAdapter, "previous");
		this.previousAdapter = null;
		// Tell the current adapter that its view will be destroyed.
		destroyAdapterQuietely(this.currentAdapter, "current");
		this.currentAdapter = null;
	}

	// IC added
	private void destroyAdapterQuietely(final AdWhirlAdapter adapter,
			final String name) {
		if (adapter != null) {
			try {
				adapter.willDestroy();
			} catch (final Throwable ex) {
				Log.e(AdWhirlUtil.ADWHIRL,
						String.format("destroying %s adapter", name), ex);
			}
		}
	}

	// Rotate in extra.cycleTime seconds
	public void rotateThreadedDelayed() {
		// IC
		if (scheduler != null && extra != null) {
			Log.d(AdWhirlUtil.ADWHIRL, "Will call rotateAd() in "
					+ extra.cycleTime + " seconds");
			scheduler.schedule(new RotateAdRunnable(this), extra.cycleTime,
					TimeUnit.SECONDS);
		}
	}

	// Remove old views and push the new one
	public void pushSubView(final ViewGroup subView) {
		// IC
		if (superViewReference == null) {
			return;
		}
		final RelativeLayout superView = superViewReference.get();
		if (superView == null) {
			return;
		}
		superView.removeAllViews();

		final RelativeLayout.LayoutParams layoutParams = new RelativeLayout.LayoutParams(
				android.view.ViewGroup.LayoutParams.WRAP_CONTENT,
				android.view.ViewGroup.LayoutParams.WRAP_CONTENT);
		layoutParams.addRule(RelativeLayout.CENTER_IN_PARENT);
		superView.addView(subView, layoutParams);

		Log.d(AdWhirlUtil.ADWHIRL, "Added subview");

		this.activeRation = nextRation;
		// IC : removed functionality
		// countImpression();
	}

	public void rollover() {
		nextRation = adWhirlManager.getRollover();
		handler.post(new HandleAdRunnable(this));
	}

	// IC : removed functionality
	// private void countImpression() {
	// if (activeRation != null) {
	// final String url = String.format(Locale.ENGLISH,
	// AdWhirlUtil.urlImpression, adWhirlManager.keyAdWhirl,
	// activeRation.nid, activeRation.type,
	// adWhirlManager.deviceIDHash, adWhirlManager.localeString,
	// AdWhirlUtil.VERSION);
	// scheduler.schedule(new PingUrlRunnable(url), 0, TimeUnit.SECONDS);
	// }
	// }

	// IC : removed functionality
	// private void countClick() {
	// if (activeRation != null) {
	// final String url = String.format(Locale.ENGLISH,
	// AdWhirlUtil.urlClick, adWhirlManager.keyAdWhirl,
	// activeRation.nid, activeRation.type,
	// adWhirlManager.deviceIDHash, adWhirlManager.localeString,
	// AdWhirlUtil.VERSION);
	// scheduler.schedule(new PingUrlRunnable(url), 0, TimeUnit.SECONDS);
	// }
	// }

	// We intercept clicks to provide raw metrics
	@Override
	public boolean onInterceptTouchEvent(final MotionEvent event) {
		switch (event.getAction()) {
		// Sending on an ACTION_DOWN isn't 100% correct... user could have
		// touched
		// down and dragged out. Unlikely though.
		case MotionEvent.ACTION_DOWN:
			Log.d(AdWhirlUtil.ADWHIRL, "Intercepted ACTION_DOWN event");
			if (activeRation != null) {
				// IC : removed functionality
				// countClick();

				if (activeRation.type == 9) {
					if (custom != null && custom.link != null) {
						// IC
						String link = custom.link;
						if (AdWhirlUtil.DEBUG) {
							Log.d(AdWhirlUtil.ADWHIRL, String.format(
									"Processing custom link \"%s\"", link));
						}
						boolean isMarket = false;
						String appPackage = null;
						// internal link to market
						// check is it correct market - may be overriden to
						// amazon for example
						final String marketPrefix = getResources().getString(
								R.string.adwhirl_market_prefix);

						if (link.toLowerCase(Locale.ENGLISH).startsWith(
								DEFAULT_MARKET_PREFIX)) {
							Log.d(AdWhirlUtil.ADWHIRL, "market url found");

							Log.d(AdWhirlUtil.ADWHIRL, "new market prefix "
									+ marketPrefix);

							if (!DEFAULT_MARKET_PREFIX.equals(marketPrefix)) {
								Log.d(AdWhirlUtil.ADWHIRL,
										"alternate market prefix");
								// convert to other market
								final Matcher matcher = defaultMarketPattern
										.matcher(link);
								if (matcher.matches()) {

									Log.d(AdWhirlUtil.ADWHIRL,
											"market app pattern matches");

									isMarket = true;
									// get first group only - its package
									appPackage = matcher.group(1);
									// reformat link
									link = getResources()
											.getString(
													R.string.adwhirl_market_internal_app_url,
													(Object) appPackage);
								}
							} else if (link.toLowerCase(Locale.ENGLISH)
									.startsWith(marketPrefix)) {
								// alternate market link found

								// extract package
								final Matcher matcher = marketPattern
										.matcher(link);
								if (matcher.matches()) {

									Log.d(AdWhirlUtil.ADWHIRL,
											"alternal market app pattern matches");

									isMarket = true;
									// get first group only - its package
									appPackage = matcher.group(1);
								}

							}

							// IC
							try {
								return startExternalActivityByUrl(link);
							} catch (final Throwable e) {
								Log.d(AdWhirlUtil.ADWHIRL, "starting link "
										+ link + " exception " + e.getMessage());
								if (isMarket) {
									// IC: use external web url
									Log.d(AdWhirlUtil.ADWHIRL,
											"Use external url");
									try {
										return startExternalActivityByUrl(getResources()
												.getString(
														R.string.adwhirl_market_external_app_url,
														(Object) appPackage));

									} catch (final Throwable e1) {
										Log.w(AdWhirlUtil.ADWHIRL,
												"Could not handle click to "
														+ custom.link, e1);
									}

								} else {
									Log.w(AdWhirlUtil.ADWHIRL,
											"Could not handle click to "
													+ custom.link, e);
								}
							}
						} else {
							// other type of URL!
							try {
								return startExternalActivityByUrl(link);
							} catch (final Throwable e) {
								Log.e(AdWhirlUtil.ADWHIRL, "starting link "
										+ link + " exception " + e.getMessage());
							}
						}

					} else {
						Log.w(AdWhirlUtil.ADWHIRL,
								"In onInterceptTouchEvent(), but custom or custom.link is null");
					}
				}
				break;
			}
		}

		// Return false so subViews can process event normally.
		return false;
	}

	// IC
	private boolean startExternalActivityByUrl(final String url) {
		Log.d(AdWhirlUtil.ADWHIRL, "starting link " + url);

		final Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(url));
		// IC
		// intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		intent.addFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
		if (activityReference == null) {
			return false;
		}
		final Activity activity = activityReference.get();
		if (activity == null) {
			return false;
		}
		activity.startActivity(intent);
		return true;
	}

	public interface AdWhirlInterface {
		public void adWhirlGeneric();
	}

	public void setAdWhirlInterface(final AdWhirlInterface i) {
		this.adWhirlInterface = i;
	}

	private static class InitRunnable implements Runnable {
		private final WeakReference<AdWhirlLayout> adWhirlLayoutReference;
		private final String keyAdWhirl;

		public InitRunnable(final AdWhirlLayout adWhirlLayout,
				final String keyAdWhirl) {
			adWhirlLayoutReference = new WeakReference<AdWhirlLayout>(
					adWhirlLayout);
			this.keyAdWhirl = keyAdWhirl;
		}

		@Override
		public void run() {
			final AdWhirlLayout adWhirlLayout = adWhirlLayoutReference.get();
			if (adWhirlLayout != null) {
				final Activity activity = adWhirlLayout.activityReference.get();
				if (activity == null) {
					return;
				}

				if (adWhirlLayout.adWhirlManager == null) {
					adWhirlLayout.adWhirlManager = new AdWhirlManager(
							new WeakReference<Context>(
									activity.getApplicationContext()),
							keyAdWhirl);
				}

				// IC
				// if (!adWhirlLayout.hasWindow) {
				if (!adWhirlLayout.isAdLive()) {
					adWhirlLayout.isScheduled = false;
					return;
				}

				adWhirlLayout.adWhirlManager.fetchConfig();
				adWhirlLayout.extra = adWhirlLayout.adWhirlManager.getExtra();

				if (adWhirlLayout.extra == null) {
					adWhirlLayout.scheduler
							.schedule(this, 30, TimeUnit.SECONDS);
				} else {
					adWhirlLayout.rotateAd();
				}
			}
		}
	}

	// IC added callback for destroy
	private static class HandleDestroy implements Runnable {
		private final WeakReference<AdWhirlLayout> adWhirlLayoutReference;

		public HandleDestroy(final AdWhirlLayout adWhirlLayout) {
			adWhirlLayoutReference = new WeakReference<AdWhirlLayout>(
					adWhirlLayout);
		}

		@Override
		public void run() {
			final AdWhirlLayout adWhirlLayout = adWhirlLayoutReference.get();
			if (adWhirlLayout != null) {
				adWhirlLayout.handleDestroy();
			}
		}
	}

	// Callback for external networks
	private static class HandleAdRunnable implements Runnable {
		private final WeakReference<AdWhirlLayout> adWhirlLayoutReference;

		public HandleAdRunnable(final AdWhirlLayout adWhirlLayout) {
			adWhirlLayoutReference = new WeakReference<AdWhirlLayout>(
					adWhirlLayout);
		}

		@Override
		public void run() {
			final AdWhirlLayout adWhirlLayout = adWhirlLayoutReference.get();
			if (adWhirlLayout != null) {
				adWhirlLayout.handleAd();
			}
		}
	}

	// Callback for pushing views from ad callbacks
	public static class ViewAdRunnable implements Runnable {
		private final WeakReference<AdWhirlLayout> adWhirlLayoutReference;
		private final ViewGroup nextView;

		public ViewAdRunnable(final AdWhirlLayout adWhirlLayout,
				final ViewGroup nextView) {
			adWhirlLayoutReference = new WeakReference<AdWhirlLayout>(
					adWhirlLayout);
			this.nextView = nextView;
		}

		@Override
		public void run() {
			final AdWhirlLayout adWhirlLayout = adWhirlLayoutReference.get();
			if (adWhirlLayout != null) {
				adWhirlLayout.pushSubView(nextView);
			}
		}
	}

	private static class RotateAdRunnable implements Runnable {
		private final WeakReference<AdWhirlLayout> adWhirlLayoutReference;

		public RotateAdRunnable(final AdWhirlLayout adWhirlLayout) {
			adWhirlLayoutReference = new WeakReference<AdWhirlLayout>(
					adWhirlLayout);
		}

		@Override
		public void run() {
			final AdWhirlLayout adWhirlLayout = adWhirlLayoutReference.get();
			if (adWhirlLayout != null) {
				adWhirlLayout.rotateAd();
			}
		}
	}

	private static class PingUrlRunnable implements Runnable {
		private final String url;

		public PingUrlRunnable(final String url) {
			this.url = url;
		}

		@Override
		public void run() {
			Log.d(AdWhirlUtil.ADWHIRL, "Pinging URL: " + url);

			final HttpClient httpClient = new DefaultHttpClient();
			final HttpGet httpGet = new HttpGet(url);

			try {
				httpClient.execute(httpGet);
			} catch (final ClientProtocolException e) {
				Log.e(AdWhirlUtil.ADWHIRL,
						"Caught ClientProtocolException in PingUrlRunnable", e);
			} catch (final IOException e) {
				Log.e(AdWhirlUtil.ADWHIRL,
						"Caught IOException in PingUrlRunnable", e);
			}
		}
	}
}
